package org.fhnw.aigs.client.GUI; import javafx.animation.FadeTransition; import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; import javafx.scene.layout.Pane; import javafx.scene.layout.RowConstraints; import javafx.scene.text.Font; import javafx.stage.Screen; import javafx.stage.Stage; import javafx.stage.WindowEvent; import javafx.util.Duration; import org.fhnw.aigs.client.communication.Settings; import org.fhnw.aigs.client.gameHandling.ClientGame; /** * This class provides represents the "frame" around a game. It is structured in * the following way:<br><ul> * <li>On the top, there is the {@link header}. It takes up 100% width and 10% * height.You can edit it's content via {@link BaseHeader#setGameNameText} and * {@link BaseHeader#setStatusLabelText}, see {@link BaseHeader}.</li> * <li>In the middle there is the actual content, the game itself. This will * take up 85 percent of the space. The content can be accessed via * {@link BaseGameWindow#getContent}.</li> * <li>The {@link BaseGameWindow#footer} is the last part which only takes up * 5%. It can be accessed via {@link BaseGameWindow#getFooter} but does not have * any functionality in the base client.</li> * </ul><br> * v1.0 Initial release<br> * v1.1 Change of handling and UI improvements<br> * v1.2 Changes of layer handling * * @author Matthias Stöckli (v1.0) * @version v1.2 (Raphael Stoeckli, 23.04.2014) */ public class BaseGameWindow extends GridPane { /** * The JavaFX scene - in some way the application window itself */ private Scene scene; /** * The stage represents the content of the scene */ private Stage primaryStage; /** * Allows to show an overlay like a loading window. Use * {@link BaseGameWindow#removeOverlay()}, * {@link BaseGameWindow#setOverlay} and * {@link BaseGameWindow#fadeOutOverlay()} for that purpose. */ private Node overlay; /** * The header, contains the game title and a status label which can be * modified */ private BaseHeader header; /** * The actual game content, most of the times an intance of a class * inheriting of {@link BaseBoard}. Use the * {@link BaseGameWindow#setContent} to set the content. */ private Node content; /** * The footer of the window. It can be changed with * {@link BaseGameWindow#setFooter} if needed. */ private Pane footer; /** * The title of the Window */ private String title; /** * This is the recommended constructor. This allows to use a stylesheet * which can then be used in other classes such as Fields or Boards. * * @param primaryStage The primary stage which is given by the main method. * @param gameStylesheetPath The (relative) path to a stylesheet (css). * Please note that the stylesheet must be stored in a folder which is used * as a source folder. Netbeans automatically adds folders to the source * folder, in other IDEs it may be necessary to add the folder to the class * path. If the stylesheet is store in the "src" folder, it should be * recognized by most IDEs immediately. * @param title The title of the game which will be displayed as the * window's title. */ public BaseGameWindow(Stage primaryStage, String gameStylesheetPath, String title) { this(primaryStage, title); scene.getStylesheets().add(gameStylesheetPath); } /** * See * {@link BaseGameWindow#BaseGameWindow(javafx.stage.Stage, java.lang.String, java.lang.String)}. * This constructor does not specify a stylesheet. * * @param primaryStage The primary stage which is given by the main method. * @param title The title of the game which will be displayed as the * window's title. */ public BaseGameWindow(Stage primaryStage, String title) { this.primaryStage = primaryStage; this.title = title; // Set the scree dimensions (4/3 of the screens width) // this makes sure that the Game Window always appears to be of the same // size. Screen screen = Screen.getPrimary(); primaryStage.setX(0); primaryStage.setY(0); primaryStage.setWidth(screen.getVisualBounds().getHeight() / 4 * 3); primaryStage.setHeight(screen.getVisualBounds().getHeight() / 4 * 3); primaryStage.setResizable(false); scene = new Scene(this); scene.getStylesheets().add("/Assets/Stylesheets/base.css"); primaryStage.setScene(scene); // F11 performs Fullscreen toggle. This is more of a gimmick. scene.setOnKeyReleased(new EventHandler<KeyEvent>() { @Override public void handle(KeyEvent t) { if (t.getCode() == KeyCode.F11) { toggleFullScreen(); } } }); // Constraints, the distribution on the horizontal level: RowConstraints headerConstraint = new RowConstraints(); headerConstraint.setPercentHeight(10); RowConstraints contentRowConstraint = new RowConstraints(); contentRowConstraint.setPercentHeight(85); RowConstraints footerConstraint = new RowConstraints(); footerConstraint.setPercentHeight(5); this.getRowConstraints().addAll(headerConstraint, contentRowConstraint, footerConstraint); // Constraints, the distribution on the vertical level ColumnConstraints leftSpaceConstraint = new ColumnConstraints(); leftSpaceConstraint.setPercentWidth(8); ColumnConstraints contentColumnConstraint = new ColumnConstraints(); contentColumnConstraint.setPercentWidth(84); ColumnConstraints rightSpaceConstraint = new ColumnConstraints(); rightSpaceConstraint.setPercentWidth(8); this.getColumnConstraints().addAll(leftSpaceConstraint, contentColumnConstraint, rightSpaceConstraint); this.getRowConstraints().add(new RowConstraints(RowConstraints.CONSTRAIN_TO_PREF)); // Load the font used in the css Font.loadFont(getClass().getResource("/Assets/Fonts/AeroviasBrasilNF.ttf").toExternalForm(), 12); // Create header pane and style it Pane headerPane = new Pane(); headerPane.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE); headerPane.getStyleClass().add("darkBackground"); // Create footer and style it footer = new Pane(); footer.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE); footer.getStyleClass().add("darkBackground"); // Create left margin and style it Pane leftSpace = new Pane(); leftSpace.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE); leftSpace.getStyleClass().add("semiDarkBackground"); // Create right margin and style it Pane rightSpace = new Pane(); rightSpace.setPrefSize(Double.MAX_VALUE, Double.MAX_VALUE); rightSpace.getStyleClass().add("semiDarkBackground"); // Define header header = new BaseHeader(title); this.add(headerPane, 0, 0, 3, 1); this.add(header, 1, 0, 1, 1); // Add left and right space. this.add(leftSpace, 0, 1, 1, 1); this.add(rightSpace, 2, 1, 1, 1); this.add(footer, 0, 2, 3, 1); primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() { @Override public void handle(WindowEvent event) { System.exit(0); } }); } /** * Activates full screen mode or deactivates it, if it already is in full * scren mode. The process will make use of the {@link javafx.application.Platform#runLater(java.lang.Runnable)} * method because this opperation must be performed on the JavaFX thread. * Please note: As the aspect ratio of normal games will be 1:1, the image * may be distortet. */ public void toggleFullScreen() { Platform.runLater(new Runnable() { @Override public void run() { boolean isFullScreen = primaryStage.isFullScreen(); if (isFullScreen) { primaryStage.setFullScreen(false); } else { primaryStage.setFullScreen(true); } } }); } /** * See {@link BaseGameWindow#header}. */ public BaseHeader getHeader() { return this.header; } /** * See {@link BaseGameWindow#footer}. */ public Pane getFooter() { return this.footer; } /** * See {@link BaseGameWindow#content}. */ public Node getContent() { return this.content; } /** * See {@link BaseGameWindow#content}. */ public void setContent(Node newContent) { this.getChildren().remove(content); this.content = newContent; this.add(content, 1, 1); content.toFront(); } /** * See {@link BaseGameWindow#header}. */ public void setHeader(BaseHeader header) { this.header = header; } /** * See {@link BaseGameWindow#footer}. */ public void setFooter(Pane footer) { this.footer = footer; } /** * See {@link BaseGameWindow#content}. */ public void removeContent() { this.getChildren().remove(content); this.content = null; } /** * Initializes the game.<br> * This method will call the settings window if no settings are defined or * if the window is configured to be visible at every startup.<br> * Depending on the settings, the setup window or the loading window (overlay) * will be enabled. An auomatic connection will be established in case * of the loading window. Call this method in the <b>start()</b> method of your game * (in Main.java or a similar calss containing the main method) * @param content The main pane of the game. This could be an instance of {@link BaseBoard} or any instance derived from {@link Node} like {@link GridPane} * @param clientGame The client game object of the game * @since v1.1 */ public void InitGame(Node content, ClientGame clientGame) { if (clientGame.getVersionString() != null) { this.primaryStage.setTitle(this.title + " " + clientGame.getVersionString()); } else { this.primaryStage.setTitle(this.title); } this.setContent(content); Settings.tryLoadSettings(true); // Open Settings window, if defined this.setOverlay(new SetupWindow(clientGame),true, true); this.primaryStage.show(); } /** * See {@link BaseGameWindow#overlay}. */ public void setOverlay(Node overlay) { setOverlay(overlay, false, true); } /** * See {@link BaseGameWindow#overlay}. */ public void setOverlay(Node overlay, boolean overrideRemoveExistingOverlay) { setOverlay(overlay, overrideRemoveExistingOverlay, true); } /** * @param distinctLayers If true, all existing overlays with the same ID as the new one will be removed * See {@link BaseGameWindow#overlay}. * @since v1.2 */ public void setOverlay(Node overlay, boolean overrideRemoveExistingOverlay, boolean distinctLayers) { if (distinctLayers == true) { String id = overlay.getId(); if (this.overlay != null) { removeOverlay(id); // Remove all overlays with the id of the new layer firs (distinct) } } else { if (this.overlay != null && overrideRemoveExistingOverlay == false) { removeOverlay(); // Remove an existing (top) overlay first } } this.overlay = overlay; try { this.add(overlay, 1, 1); } catch (Exception e) { } overlay.toFront(); content = null; } /** * Removes the top layer of the overlay. Use this method if joining or * creating a party is failed and the setup window has to be showed again.<br> * Do not use it to start the party. Use then {@link BaseGameWindow#fadeOutOverlay()} instead. * See {@link BaseGameWindow#overlay}. * @since v1.1 */ private void removeOverlay() { try { this.getChildren().remove(this.getChildren().size() - 1); } catch (Exception e) { } } /** * Remove all layers with the specified ID. Uste this Method to avoid double defined layers.<br> * Do not use it to start the party. Use then {@link BaseGameWindow#fadeOutOverlay()} instead. * See {@link BaseGameWindow#overlay}. * @since v1.2 * @param id ID(s) to remove */ private void removeOverlay(String id) { int length = this.getChildren().size(); for(int i = length - 1; i >= 0; i-- ) // Inverse i to avoid unwanted shifting while removing { if (this.getChildren().get(i).getId() == null) { continue; } // Skip unnamed layers if (this.getChildren().get(i).getId().equals(id)) { this.getChildren().remove(i); } } } /** * This method allows to fade out an overlay set by {@link BaseGameWindow#setOverlay}.<br> * Use this method to start the party. If an overlay has to be replaced with * another one, use method {@link BaseGameWindow#removeOverlay()} instead. */ public void fadeOutOverlay() { FadeTransition ft = new FadeTransition(Duration.seconds(1), overlay); ft.setFromValue(1); ft.setToValue(0); ft.play(); ft.setOnFinished(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent args) { overlay.toBack(); overlay = null; } }); } }